---
title: What's new in Riverpod 3.0
version: 1
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import { AutoSnippet } from "/src/components/CodeSnippet";
import { Link } from "/src/components/Link";


Welcome to Riverpod 3.0!  
This update includes many long-due features, bug fixes, and simplifications of the API.

This version is a transition period toward a simpler, unified Riverpod.  

:::caution
This version contains a few life-cycle changes. Those could break your app in subtle ways. Upgrade carefully.  
For the migration guide, please refer to the [migration page](3.0_migration).
:::

Some of the key highlights include:

- [Offline persistence (experimental)](#offline-persistence-experimental) - Providers can now opt-in to be persisted to a database
- [Mutations (experimental)](#mutations-experimental) - A new mechanism to enable interfaces to react to side-effects
- [Automatic retry](#automatic-retry) - Providers now refresh when they fail, with exponential backoff
- [`Ref.mounted`](#refmounted) - Similar to `BuildContext.mounted`, but for `Ref`.
- [Generic support (code-generation)](#generic-support-code-generation) - Generated providers can now define type parameters
- [Pause/Resume support](#pauseresume-support) - Temporarily pause a listener when using `ref.listen`
- [Unification of the Public APIs](#unification-of-the-public-apis) - Behaviors are unified and duplicate interfaces are fused
- [Provider life-cycle changes](#provider-life-cycle-changes) - Slight tweaks to how providers behave, to better fit modern code
- [New testing utilities](#new-testing-utilities):
  - [`ProviderContainer.test`](#providercontainertest) - A test util that creates a container and automatically disposes it after the test ends.
  - [`NotifierProvider.overrideWithBuild`](#notifierprovideroverridewithbuild) - A way to mock only `Notifier.build`, without mocking the whole notifier.
  - [`Future/StreamProvider.overrideWithValue`](#futurestreamprovideroverridewithvalue) - The old utilities are back
  - [`WidgetTester.container`](#widgettestercontainer) - A helper method to obtain the `ProviderContainer` inside widget tests
- [Statically safe scoping](#statically-safe-scoping-code-generation-only) - New lint rules are added to detect when an override is missing

## Offline persistence (experimental)

:::info
This feature is experimental and not yet stable.
It is usable, but the API may change in breaking ways without a major version bump.
:::

Offline persistence is a new feature that enables caching a provider locally on the device.
Then, when the application is closed and reopened, the provider can be restored from the cache.  
Offline persistence is opt-in, and supported by all "Notifier" providers,
and regardless of if you use code generation or not.

Riverpod only includes interfaces to interact with a database. It does not include a database itself.
You can use any database you want, as long as it implements the interfaces.  
An official package for SQLite is maintained: [riverpod_sqflite](https://pub.dev/packages/riverpod_sqflite).

As a short demo, here's how you can use offline persistence:  

<AutoSnippet
  language="dart"
  codegen={`
    @riverpod
    Future<JsonSqFliteStorage> storage(Ref ref) async {
      // Initialize SQFlite. We should share the Storage instance between providers.
      return JsonSqFliteStorage.open(
        join(await getDatabasesPath(), 'riverpod.db'),
      );
    }
    
    /// A serializable Todo class. We're using Freezed for simple serialization.
    @freezed
    abstract class Todo with _$Todo {
      const factory Todo({
        required int id,
        required String description,
        required bool completed,
      }) = _Todo;
    
      factory Todo.fromJson(Map<String, dynamic> json) => _$TodoFromJson(json);
    }
    
    @riverpod
    @JsonPersist()
    class TodosNotifier extends _$TodosNotifier {
      @override
      FutureOr<List<Todo>> build() async {
        // We call persist at the start of our 'build' method.
        // This will:
        // - Read the DB and update the state with the persisted value the first
        //   time this method executes.
        // - Listen to changes on this provider and write those changes to the DB.
        persist(
          // We pass our JsonSqFliteStorage instance. No need to "await" the Future.
          // Riverpod will take care of that.
          ref.watch(storageProvider.future),
          // By default, state is cached offline only for 2 days.
          // We can optionally uncomment the following line to change cache duration.
          // options: const StorageOptions(cacheTime: StorageCacheTime.unsafe_forever),
        );
  
        // We asynchronously fetch todos from the server.
        // During the await, the persisted todo list will be available.
        // After the network request completes, the server state will take precedence
        // over the persisted state.
        final todos = await fetchTodos();
        return todos;
      }
  
      Future<void> add(Todo todo) async {
        // When modifying the state, no need for any extra logic to persist the change.
        // Riverpod will automatically cache the new state and write it to the DB.
        state = AsyncData([...await future, todo]);
      }
    }
  `}
  
  raw={`
  // A example showcasing JsonSqFliteStorage without code generation.
  final storageProvider = FutureProvider<JsonSqFliteStorage>((ref) async {
    // Initialize SQFlite. We should share the Storage instance between providers.
    return JsonSqFliteStorage.open(
      join(await getDatabasesPath(), 'riverpod.db'),
    );
  });
  
  /// A serializable Todo class.
  class Todo {
    const Todo({
      required this.id,
      required this.description,
      required this.completed,
    });
  
    Todo.fromJson(Map<String, dynamic> json)
        : id = json['id'] as int,
          description = json['description'] as String,
          completed = json['completed'] as bool;
  
    final int id;
    final String description;
    final bool completed;
  
    Map<String, dynamic> toJson() {
      return {
        'id': id,
        'description': description,
        'completed': completed,
      };
    }
  }
  
  final todosProvider =
      AsyncNotifierProvider<TodosNotifier, List<Todo>>(TodosNotifier.new);
  
  class TodosNotifier extends AsyncNotifier<List<Todo>>{
    @override
    FutureOr<List<Todo>> build() async {
      // We call persist at the start of our 'build' method.
      // This will:
      // - Read the DB and update the state with the persisted value the first
      //   time this method executes.
      // - Listen to changes on this provider and write those changes to the DB.
      persist(
        // We pass our JsonSqFliteStorage instance. No need to "await" the Future.
        // Riverpod will take care of that.
        ref.watch(storageProvider.future),
        // A unique key for this state.
        // No other provider should use the same key.
        key: 'todos',
        // By default, state is cached offline only for 2 days.
        // We can optionally uncomment the following line to change cache duration.
        // options: const StorageOptions(cacheTime: StorageCacheTime.unsafe_forever),
        encode: jsonEncode,
        decode: (json) {
          final decoded = jsonDecode(json) as List;
          return decoded
              .map((e) => Todo.fromJson(e as Map<String, Object?>))
              .toList();
        },
      );
  
        // We asynchronously fetch todos from the server.
        // During the await, the persisted todo list will be available.
        // After the network request completes, the server state will take precedence
        // over the persisted state.
        final todos = await fetchTodos();
        return todos;
    }
  
    Future<void> add(Todo todo) async {
      // When modifying the state, no need for any extra logic to persist the change.
      // Riverpod will automatically cache the new state and write it to the DB.
      state = AsyncData([...await future, todo]);
    }
  }
  `}
></AutoSnippet>


## Mutations (experimental)

:::info
This feature is experimental and not yet stable.
It is usable, but the API may change in breaking ways without a major version bump.
:::

A new feature called "mutations" is introduced in Riverpod 3.0.  
This feature solves two problems:
- It empowers the UI to react to "side-effects" (such as form submissions, button clicks, etc),
  to enable it to show loading/success/error messages.
  Think "Show a toast when a form is submitted successfully".
- It solves an issue where `onPressed` callbacks combined with [Ref.read] and <Link documentID="concepts2/auto_dispose" />
  could cause providers to be disposed while a side-effect is still in progress.

The TL;DR is, a new [Mutation] object is added. It is declared as a top-level final variable,
like providers:

```dart
final addTodoMutation = Mutation<void>();
```

After that, your UI can use `ref.listen`/`ref.watch` to listen to the state of mutations:

```dart
class AddTodoButton extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Listen to the status of the "addTodo" side-effect
    final addTodo = ref.watch(addTodoMutation);

    return switch (addTodo) {
      // No side-effect is in progress
      // Let's show a submit button
      MutationIdle() => ElevatedButton(
        // Trigger the side-effect on click
        onPressed: () {
          // TODO see explanation after the code snippet
        },
        child: const Text('Submit'),
      ),
      // The side-effect is in progress. We show a spinner
      MutationPending() => const CircularProgressIndicator(),
      // The side-effect failed. We show a retry button
      MutationError() => ElevatedButton(
        onPressed: () {
          // TODO see explanation after the code snippet
        },
        child: const Text('Retry'),
      ),
      // The side-effect was successful. We show a success message
      MutationSuccess() => const Text('Todo added!'),
    };
  }
}
```

Last but not least, inside our `onPressed` callback, we can trigger our side-effect as
followed:

```dart
onPressed: () {
  addTodoMutation.run(ref, (tsx) async {
    // This is where we run our side-effect.
    // Here, we typically obtain a Notifier and call a method on it.
    await tsx.get(todoListProvider.notifier).addTodo('New Todo');
  });
}
```

:::note
Note how we called [tsx.get] here instead of [Ref.read].  
This is a feature unique to mutations. That [tsx.get] obtains the state of a provider,
but keep it alive until the mutation is completed.
:::


## Automatic retry

Starting 3.0, providers that fail during initialization will automatically retry.
The retry is done with an exponential backoff, and the provider will be retried
until it succeeds or is disposed. This helps when an operation fails due to a temporary issue, such as a lack of network connection.

The default behavior retries any error, and starts with a 200ms delay that
doubles after each retry up to 6.4 seconds.  
This can be customized for all providers on [ProviderContainer]/[ProviderScope] by passing a `retry` parameter:

<Tabs>
<TabItem value="ProviderScope" label="ProviderScope" defaultValue>

```dart
void main() {
  runApp(
    ProviderScope(
      // You can customize the retry logic, such as to skip
      // specific errors or add a limit to the number of retries
      // or change the delay
      retry: (retryCount, error) {
        if (error is SomeSpecificError) return null;
        if (retryCount > 5) return null;

        return Duration(seconds: retryCount * 2);
      },
      child: MyApp(),
    ),
  );
}
```

</TabItem>
<TabItem value="ProviderContainer" label="ProviderContainer" defaultValue>

```dart
void main() {
  final container = ProviderContainer(
    // You can customize the retry logic, such as to skip
    // specific errors or add a limit to the number of retries
    // or change the delay
    retry: (retryCount, error) {
      if (error is SomeSpecificError) return null;
      if (retryCount > 5) return null;

      return Duration(seconds: retryCount * 2);
    },
  );
}
```

</TabItem>
</Tabs>

Alternatively, this can be configured on a per-provider basis by passing a `retry` parameter to the provider constructor:

<AutoSnippet
  language="dart"
  codegen={`
  Duration retry(int retryCount, Object error) {
    if (error is SomeSpecificError) return null;
    if (retryCount > 5) return null;
  
    return Duration(seconds: retryCount * 2);
  }
  
  @Riverpod(retry: retry)
  class TodoList extends _$TodoList {
    @override
    List<Todo> build() => [];
  }
  `}
  
  raw={`
  final todoListProvider = NotifierProvider<TodoList, List<Todo>>(
    TodoList.new,
    retry: (retryCount, error) {
      if (error is SomeSpecificError) return null;
      if (retryCount > 5) return null;
    
      return Duration(seconds: retryCount * 2);
    },
  );
  `}
></AutoSnippet>


## `Ref.mounted`

The long-awaited `Ref.mounted` is finally here! It is similar to `BuildContext.mounted`, but for `Ref`.

You can use it to check if a provider is still mounted after an async operation:

<AutoSnippet
  language="dart"
  codegen={`
  @riverpod
  class TodoList extends _$TodoList {
    @override
    List<Todo> build() => [];
    
    Future<void> addTodo(String title) async {
      // Post the new todo to the server
      final newTodo = await api.addTodo(title);
      // Check if the provider is still mounted
      // after the async operation
      if (!ref.mounted) return;
    
      // If it is, update the state
      state = [...state, newTodo];
    }
  }
  `}
  
  raw={`
  class TodoList extends Notifier<List<Todo>> {
    @override
    List<Todo> build() => [];
    
    Future<void> addTodo(String title) async {
      // Post the new todo to the server
      final newTodo = await api.addTodo(title);
      // Check if the provider is still mounted
      // after the async operation
      if (!ref.mounted) return;
    
      // If it is, update the state
      state = [...state, newTodo];
    }
  }
  `}
></AutoSnippet>

For this to work, quite a few life-cycle changes were necessary.  
Make sure to read the [life-cycle changes](#provider-life-cycle-changes) section.

## Generic support (code-generation)

When using code generation, you can now define type parameters for your generated providers.
Type parameters work like any other provider parameter, and need to be passed
when watching the provider.

```dart
@riverpod
T multiply<T extends num>(T a, T b) {
  return a * b;
}

// ...

int integer = ref.watch(multiplyProvider<int>(2, 3));
double decimal = ref.watch(multiplyProvider<double>(2.5, 3.5));
```

## Pause/Resume support

In 2.0, Riverpod already had some form of pause/resume support, but it was fairly limited.
With 3.0, all `ref.listen` listeners can be manually paused/resumed on demand:

```dart
final subscription = ref.listen(
  todoListProvider,
  (previous, next) {
    // Do something with the new value
  },
);

subscription.pause();
subscription.resume();
```

At the same time, Riverpod now pauses providers in various situations:
- When a provider is no-longer visible, it is paused
  (Based off [TickerMode]).
- When a provider rebuilds, its subscriptions are paused until the rebuild completes.
- When a provider is paused, all of its subscriptions are paused too.

See the [life-cycle changes](#provider-life-cycle-changes) section for more details.

## Unification of the Public APIs

One goal of Riverpod 3.0 is to simplify the API. This includes:
- Highlighting what is recommended and what is not
- Removing needless interface duplicates
- Making sure all functionalities function in a consistent way

For this sake, a few changes were made:

### [StateProvider]/[StateNotifierProvider] and [ChangeNotifierProvider] are discouraged and moved to a different import

Those providers are not removed, but simply moved to a different import.
Instead of:

```dart
import 'package:riverpod/riverpod.dart';
```
You should now use:
```dart
import 'package:riverpod/legacy.dart';
```

This is to highlight that those providers are not recommended anymore.  
At the same time, those are preserved for backward compatibility.

### AutoDispose interfaces are removed

No, the "auto-dispose" feature isn't removed. This only concerns the interfaces.
In 2.0, all providers, Refs and Notifiers were duplicated for the sake of auto-dispose (
`Ref` vs `AutoDisposeRef`, `Notifier` vs `AutoDisposeNotifier`, etc).
This was done for the sake of having a compilation error in some edge-cases, but came
at the cost of a worse API.

In 3.0, the interfaces are unified, and the previous compilation error is now implemented
as a lint rule (using [riverpod_lint]). 
What this means concretely is that you can replace all references to
`AutoDisposeNotifier` with `Notifier`. The behavior of your code should not change.

```diff
final provider = NotifierProvider.autoDispose<MyNotifier, int>(
  MyNotifier.new,
);

- class MyNotifier extends AutoDisposeNotifier<int> {
+ class MyNotifier extends Notifier<int> {
}
```

### "FamilyNotifier" and "Notifier" are fused

Similarly to the previous point, the `FamilyNotifier` and `Notifier` interfaces
are now fused.

Long story short, instead of:

```dart
final provider = NotifierProvider.family<CounterNotifier, int, Argument>(
  MyNotifier.new,
);

class CounterNotifier extends FamilyNotifier<int, Argument> {
  @override
  int build(Argument arg) => 0;
}
```

We now do:

```dart
final provider = NotifierProvider.family<CounterNotifier, int, Argument>(
  CounterNotifier.new,
);

class CounterNotifier extends Notifier<int> {
  CounterNotifier(this.arg);
  final Argument arg;
  @override
  int build() => 0;
}
```

This means that instead of `Notifier`+`FamilyNotifier`+`AutoDisposeNotifier`+`AutoDisposeFamilyNotifier`,
we always use the `Notifier` class.

This change has no impact on code-generation.

### One `Ref` to rule them all

In Riverpod 2.0, each provider came with its own [Ref] subclass (`FutureProviderRef`, `StreamProviderRef`, etc).  
Some `Ref` had `state` property, some a `future`, or a `notifier`, etc. 
Although useful, this was a lot of complexity for not much gain. One of the reasons
for that is because [Notifier]s already have the extra properties it had,
so the interfaces were redundant.

In 3.0, `Ref` is unified. No more generic parameter such as `Ref<T>`,
no more `FutureProviderRef`. We only have one thing: `Ref`.
What this means in practice is, the syntax for generated providers is simplified:

```diff
-Example example(ExampleRef ref) {
+Example example(Ref ref) {
  return Example();
}
```


:::info
This does not concern [WidgetRef], which is intact.  
[Ref] and [WidgetRef] are two different things.
:::

### All `updateShouldNotify` now use `==`

`updateShouldNotify` is a method that is used to determine if a provider should
notify its listeners when a state change occurs. 
But in 2.0, the implementation of this method varied quite a bit between providers.
Some providers used `==`, some `identical`, and some more complex logic.

Starting 3.0, all providers use `==` to filter notifications.

This can impact you in a few ways:
- Some of your providers may not notify their listeners anymore
  in certain situations.
- Some listeners may be notified more often than before.
- If you have a large data class that overrides `==`, you may see a small
  performance impact.

The most common case where you will be impacted is when using [StreamProvider]/[StreamNotifier],
as events of the stream are now filtered using `==`.

If you are impacted by those changes, you can override `updateShouldNotify` to
use a custom implementation:

<AutoSnippet
  language="dart"
  codegen={`
  @riverpod
  class TodoList extends _$TodoList {
    @override
    Stream<Todo> build() => Stream(...);
  
    @override
    bool updateShouldNotify(AsyncValue<Todo> previous, AsyncValue<Todo> next) {
      // Custom implementation
      return true;
    }
  }
  `}
  
  raw={`
  class TodoList extends StreamNotifier<Todo> {
    @override
    Stream<Todo> build() => Stream(...);
  
    @override
    bool updateShouldNotify(AsyncValue<Todo> previous, AsyncValue<Todo> next) {
      // Custom implementation
      return true;
    }
  }
  `}
></AutoSnippet>

## Provider life-cycle changes

### Refs and Notifiers can no-longer be interacted with after they have been disposed

In 2.0, in some edge-cases you could still interact with things like [Ref] or [Notifier]
after they were disposed. This was not intended and caused various severe bugs.

In 3.0, Riverpod will throw an error if you try to interact with a disposed Ref/Notifier.

You can use [Ref.mounted] to check if a Ref/Notifier is still usable.

```dart
final provider = FutureProvider<int>((ref) async {
  await Future.delayed(Duration(seconds: 1));
  // Abort the provider if it has been disposed during the await.
  // You can throw whatever you want and ignore this exception in your error reporting tools.
  if (!ref.mounted) throw MyException();
  return 42;
});
```

### When reading a provider results in an exception, the error is now wrapped in a ProviderException

Before, if a provider threw an error, Riverpod would sometimes rethrow that error directly:

<AutoSnippet
  language="dart"
  codegen={`
  @riverpod
  Future<int> example(Ref ref) async {
    throw StateError('Error');
  }
  
  // ...
  ElevatedButton(
    onPressed: () async {
      // This will rethrow the StateError
      ref.read(exampleProvider).requireValue;
    
      // This also rethrows the StateError
      await ref.read(exampleProvider.future);
    },
    child: Text('Click me'),
  );
  `}
  
  raw={`
  final exampleProvider = FutureProvider<int>((ref) async {
    throw StateError('Error');
  });
  
  // ...
  ElevatedButton(
    onPressed: () async {
      // This will rethrow the StateError
      ref.read(exampleProvider).requireValue;
    
      // This also rethrows the StateError
      await ref.read(exampleProvider.future);
    },
    child: Text('Click me'),
  );
  `}
></AutoSnippet>


In 3.0, this is changed. Instead, the error will be encapsulated in a `ProviderException`
that contains both the original error and its stack trace.

:::info
`AsyncValue.error`, `ref.listen(..., onError: ...)` and [ProviderObserver]s  are unaffected by this change,
and will still receive the unaltered error.
:::

This has multiple benefits:
- Debugging is improved, as we have a much better stack trace
- It is now possible to determine if a provider failed, or
  if it is in error state because it depends on another provider that failed.

For example, a [ProviderObserver] can use this to avoid logging the same error twice:

```dart
class MyObserver extends ProviderObserver {
  @override
  void providerDidFail(ProviderObserverContext context, Object error, StackTrace stackTrace) {
    if (error is ProviderException) {
      // The provider didn't fail directly, but instead depends on a failed provider.
      // The error was therefore already logged.
      return;
    }

    // Log the error
    print('Provider failed: $error');
  }
}
```

This is used internally by Riverpod in its automatic retry mechanism. The default automatic retry
ignores `ProviderException`s:

```dart
ProviderContainer(
  // Example of the default retry behavior
  retry: (retryCount, error) {
    if (error is ProviderException) return null;

    // ...
  },
);
```

### Listeners inside widgets that are not visible are now paused

Now that Riverpod has a way to [pause listeners](#pauseresume-support), Riverpod uses that to
natively pauses listeners when the widget is not visible. In practice what this means is: Providers that are not used by the visible widget tree
are paused.

As a concrete example, consider an application with two routes:
- A home page, listening to a websocket using a provider
- A settings page, which does not rely on that websocket


In typical applications, a user first opens the home page _and then_ opens the settings page.
This means that while the settings page is open, the homepage is also open, but not visible.

In 2.0, the homepage would actively keep listening to the websocket.  
In 3.0, the websocket provider will instead be paused, possibly saving resources.

**How it works:**  
Riverpod relies on [TickerMode] to determine if a widget is visible or not. And when
false, all listeners of a [Consumer] are paused.

It also means that you can rely on [TickerMode] yourself to manually control
the pause behavior of your consumers. You can voluntarily set the value to true/false
to forcibly resume/pause listeners:

```dart
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return TickerMode(
      enabled: false, // This will pause the listeners
      child: Consumer(
        builder: (context, ref, child) {
          // This "watch" will be paused
          // until TickerMode is set to true
          final value = ref.watch(myProvider);
          return Text(value.toString());
        },
      ),
    );
  }
}
```

### If a provider is only used by paused providers, it is paused too

Riverpod 2.0 already had some form of pause/resume support. But it was limited and failed 
to cover some edge-cases.  
Consider:

<AutoSnippet
  language="dart"
  codegen={`
  @riverpod
  int example(Ref ref) {
    ref.keepAlive();
    ref.onCancel(() => print('paused'));
    ref.onResume(() => print('resumed'));
    return 0;
  }
  `}
  
  raw={`
  final exampleProvider = Provider<int>((ref) {
    ref.onCancel(() => print('paused'));
    ref.onResume(() => print('resumed'));
    return 0;
  });
  `}
></AutoSnippet>

In 2.0, if you were to call `ref.read` once on this provider,
the state of the provider would be maintained, but 'paused' will be printed. This is because
calling `ref.read` does not "listen" to the provider. And since the provider is not "listened"
to, it is paused.

This is useful to pause providers that are currently not used! 
The problem is that in many cases, this optimization does not work.  
For example, your provider could be used indirectly through another provider.

<AutoSnippet
  language="dart"
  codegen={`
  @riverpod
  int another(Ref ref) {
    ref.keepAlive();
    return ref.watch(exampleProvider);
  }
  
  class MyWidget extends ConsumerWidget {
    @override
    Widget build(BuildContext context, WidgetRef ref) {
      return Button(
        onPressed: () {
          ref.read(anotherProvider);
        },
        child: Text('Click me'),
      );
    }
  }
  `}
  
  raw={`
  final anotherProvider = Provider<int>((ref) {
    return ref.watch(exampleProvider);
  });
   
   class MyWidget extends ConsumerWidget {
    @override
    Widget build(BuildContext context, WidgetRef ref) {
      return Button(
        onPressed: () {
          ref.read(anotherProvider);
        },
        child: Text('Click me'),
      );
    }
  }
  `}
></AutoSnippet>

In this scenario, if we click on the button once,
then `anotherProvider` will start listening to our `exampleProvider`. But `anotherProvider`
is no-longer used and will be paused. Yet `exampleProvider` will not be paused,
because it thinks that it is still being used.  
As such, clicking on the button will not print 'paused' anymore. 

In 3.0, this is fixed. If a provider is only used by paused providers, it is paused too.

### When a provider rebuilds, its previous subscriptions now are kept until the rebuild completes

In 2.0, there was a known inconvenience when using asynchronous providers
combined with 'auto-dispose'.

Specifically, when an asynchronous provider watches an auto-dispose provider
after an `await`, the "auto dispose" could be triggered unexpectedly.

Consider:  
<AutoSnippet
  language="dart"
  codegen={`
  @riverpod
  Stream<int> autoDispose(Ref ref) {
    ref.onDispose(() => print('disposed'));
    ref.onCancel(() => print('paused'));
    ref.onResume(() => print('resumed'));
    // A stream that emits a value every second
    return Stream.periodic(Duration(seconds: 1), (i) => i);
  }
  
  @riverpod
  Future<int> asynchronousExample(Ref ref) async {
    print('Before async gap');
    // An async gap inside a provider ; typically an API call.
    // This will dispose the "autoDispose" provider
    // before the async operation is completed
    await null;
    
    print('after async gap');
    // We listen to our auto-dispose provider
    // after the async operation
    return ref.watch(autoDisposeProvider.future);
  }
  
  void main() {
    final container = ProviderContainer();
    // This will print 'disposed' every second,
    // and will constantly print 0
    container.listen(asynchronousExampleProvider, (_, value) {
      if (value is AsyncData) print('\${value.value}\\n----');
    });
  }
  `}
  
  raw={`
  final autoDisposeProvider = StreamProvider.autoDispose<int>((ref) {
    ref.onDispose(() => print('disposed'));
    ref.onCancel(() => print('paused'));
    ref.onResume(() => print('resumed'));
    // A stream that emits a value every second
    return Stream.periodic(Duration(seconds: 1), (i) => i);
  });
  
  final asynchronousExampleProvider = FutureProvider<int>((ref) async {
    print('Before async gap');
    // An async gap inside a provider ; typically an API call.
    // This will dispose the "autoDispose" provider
    // before the async operation is completed
    await null;
    
    print('after async gap');
    // We listen to our auto-dispose provider
    // after the async operation
    return ref.watch(autoDisposeProvider.future);
  });
  
  void main() {
    final container = ProviderContainer();
    // This will print 'disposed' every second,
    // and will constantly print 0
    container.listen(asynchronousExampleProvider, (_, value) {
      if (value is AsyncData) print('\${value.value}\\n----');
    });
  }
  `}
></AutoSnippet>

In you run this on [Dartpad](https://dartpad.dev/), you will see that its prints:

```
// First print
Before async gap
after async gap
0
---- // Second and after prints
paused
Before async gap
disposed // The 'autoDispose' provider was disposed during the async gap!
after async gap
0
----
paused
Before async gap
disposed
after async gap
0
----
... // And so on every second
```

As you can see, this consistently prints `0` every second,
because the `autoDispose` provider repeatedly gets disposed during the async gap. 
A workaround was to move the `ref.watch` call before the `await` statement.
But this is error prone, not very intuitive, and not always possible.

In 3.0, this is fixed by delaying the disposal of listeners.  
When a provider rebuilds, instead of immediately removing all of its listeners,
it [pauses](#pauseresume-support) them.

The exact same code will now instead print:

```
// First print
Before async gap
after async gap
0
----
paused
Before async gap
after async gap
resumed
1
----
paused
Before async gap
after async gap
resumed
2
----
... // And so on every second
```

### Exceptions in providers are rethrown as a `ProviderException`.

For the sake of differentiating between "a provider failed" from "a provider is depending on a failed provider",
Riverpod 3.0 now wraps exceptions in a `ProviderException` that contains the original.

This means that if you catch errors in your providers, you will need to update your try/catch to inspect
the content of `ProviderException`:

```dart
try {
  ref.watch(failingProvider);
} on ProviderException catch (e) {
  switch (e.exception) {
    case SomeSpecificError():
      // Handle the specific error
    default:
      // Handle other errors
      rethrow;
  }
}
```

## New testing utilities

### `ProviderContainer.test`

In 2.0, typical testing code would rely on a custom-made utility called `createContainer`.  
In 3.0, this utility is now part of Riverpod, and is called `ProviderContainer.test`.
It creates a new container, and automatically disposes it after the test ends.

```dart
void main() {
  test('My test', () {
    final container = ProviderContainer.test();
    // Use the container
    // ...
    // The container is automatically disposed after the test ends
  });
}
```

You can safely do a global search-and-replace for `createContainer` to `ProviderContainer.test`.

### `NotifierProvider.overrideWithBuild`

It is now possible to mock only the `Notifier.build` method, without mocking the whole notifier.
This is useful when you want to initialize your notifier with a specific state, but still want to
use the original implementation of the notifier.

<AutoSnippet
  language="dart"
  codegen={`
    @riverpod
    class MyNotifier extends _$MyNotifier {
      @override
      int build() => 0;
    
      void increment() {
        state++;
      }
    }
    
    void main() {
      final container = ProviderContainer.test(
        overrides: [
          myProvider.overrideWithBuild((ref) {
            // Mock the build method to start at 42.
            // The "increment" method is unaffected.
            return 42;
          }),
        ],
      );
    }
  `}
  
  raw={`
    class MyNotifier extends Notifier<int> {
      @override
      int build() => 0;
    
      void increment() {
        state++;
      }
    }
    
    final myProvider = NotifierProvider<MyNotifier, int>(MyNotifier.new);
    
    void main() {
      final container = ProviderContainer.test(
        overrides: [
          myProvider.overrideWithBuild((ref) {
            // Mock the build method to start at 42.
            // The "increment" method is unaffected.
            return 42;
          }),
        ],
      );
    }
  `}
></AutoSnippet>

### `Future/StreamProvider.overrideWithValue`

A while back, `FutureProvider.overrideWithValue` and `StreamProvider.overrideWithValue`
were removed "temporarily" from Riverpod.  
They are finally back!

<AutoSnippet
  language="dart"
  codegen={`
    @riverpod
    Future<int> myFutureProvider() async {
      return 42;
    }
    
    void main() {
      final container = ProviderContainer.test(
        overrides: [
          // Initializes the provider with a value.
          // Changing the override will update the value.
          myFutureProvider.overrideWithValue(AsyncValue.data(42)),
        ],
      );
    }
  `}
  
  raw={`
    final myFutureProvider = FutureProvider<int>((ref) async {
      return 42;
    });
    
    void main() {
      final container = ProviderContainer.test(
        overrides: [
          // Initializes the provider with a value.
          // Changing the override will update the value.
          myFutureProvider.overrideWithValue(AsyncValue.data(42)),
        ],
      );
    }
  `}
></AutoSnippet>

### `WidgetTester.container`

A simple way to access the `ProviderContainer` in your widget tree.

```dart
void main() {
  testWidgets('can access a ProviderContainer', (tester) async {
    await tester.pumpWidget(const ProviderScope(child: MyWidget()));
    ProviderContainer container = tester.container();
  });
}
```

See the [WidgetTester.container] extension for more information.

## Custom ProviderListenables

It is now possible to create custom [ProviderListenable]s in Riverpod 3.0.
This is doable using [SyncProviderTransformerMixin].

The following example implements a variable of `provider.select`,
where the callback returns a boolean instead of the selected value.

```dart
final class Where<T> with SyncProviderTransformerMixin<T, T> {
  Where(this.source, this.where);
  @override
  final ProviderListenable<T> source;
  final bool Function(T previous, T value) where;

  @override
  ProviderTransformer<T, T> transform(
    ProviderTransformerContext<T, T> context,
  ) {
     return ProviderTransformer(
       initState: (_) => context.sourceState.requireValue,
       listener: (self, previous, next) {
         if (where(previous, next))
           self.state = next;
       },
     );
  }
}

extension<T> on ProviderListenable<T> {
  ProviderListenable<T> where(
    bool Function(T previous, T value) where,
  ) => Where<T>(this, where);
}
```

Used as `ref.watch(provider.where((previous, value) => value > 0))`.

## Statically safe scoping (code-generation only)

Through [riverpod_lint], Riverpod now includes a way to detect when scoping is used incorrectly.
This lints detects when an override is missing, to avoid runtime errors.

Consider:

```dart
// A typical "scoped provider"
@Riverpod(dependencies: [])
Future<int> myFutureProvider() => throw UnimplementedError();
```

To use this provider, you have two options.  
If neither of the following options are used, the provider will throw an error at runtime.

- Override the provider using `ProviderScope` before using it:
  ```dart
  class MyWidget extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return ProviderScope(
        overrides: [
          myFutureProvider.overrideWithValue(AsyncValue.data(42)),
        ],
        // A consumer is necessary to access the overridden provider
        child: Consumer(
          builder: (context, ref, child) {
            // Use the provider
            final value = ref.watch(myFutureProvider);
            return Text(value.toString());
          },
        ),
      );
    }
  }
  ```
- Specify `@Dependencies` on whatever uses the scoped provider to indicate that it 
  depends on it.
  ```dart
  @Dependencies([myFuture])
  class MyWidget extends ConsumerWidget {
    @override
    Widget build(BuildContext context, WidgetRef ref) {
      // Use the provider
      final value = ref.watch(myFutureProvider);
      return Text(value.toString());
    }
  }
  ```
  After specifying `@Dependencies`, all usages of `MyWidget` will
  require the same two options as above:
  - Either override the provider using `ProviderScope` before using `MyWidget`
    ```dart
    void main() {
      runApp(
        ProviderScope(
          overrides: [
            myFutureProvider.overrideWithValue(AsyncValue.data(42)),
          ],
          child: MyWidget(),
        ),
      );
    }
    ```
  - Or specify `@Dependencies` on whatever uses `MyWidget` to indicate that it depends on it.
    ```dart
    @Dependencies([myFuture])
    class MyApp extends ConsumerWidget {
      @override
      Widget build(BuildContext context, WidgetRef ref) {
         // MyApp indirectly uses scoped providers through MyWidget
         return MyWidget();
      }
    }
    ```

## Other changes

### AsyncValue

[AsyncValue] received various changes.

* It is now "sealed". This enables exhaustive pattern matching:
  ```dart
  AsyncValue<int> value;
  switch (value) {
    case AsyncData():
      print('data');
    case AsyncError():
      print('error');
    case AsyncLoading():
      print('loading');
    // No default case needed
  }
  ```
* `valueOrNull` has been renamed to `value`.
  The old `value` is removed, as its behavior related to errors
  was odd.
  To migrate, do a global search-and-replace of `valueOrNull` -> `value`.
* `AsyncValue.isFromCache` has been added.  
  This flag is set when a value is obtained through offline persistence.
  It enables your UI to differentiate state coming from the database
  and state from the server.
* An optional `progress` property is available on `AsyncLoading`.
  This enables your providers to define the current progress for a
  request:
  <AutoSnippet
    language="dart"
    codegen={`
      @riverpod
      class MyNotifier extends _$MyNotifier {
        @override
        Future<User> build() async {
          // You can optionally pass a "progress" to AsyncLoading
          state = AsyncLoading(progress: .0);
          await fetchSomething();
          state = AsyncLoading(progress: 0.5);
          
          return User();
        }
      }
    `}
    
    raw={`
      class MyNotifier extends AsyncNotifier<User> {
        @override
        Future<User> build() async {
          // You can optionally pass a "progress" to AsyncLoading
          state = AsyncLoading(progress: .0);
          await fetchSomething();
          state = AsyncLoading(progress: 0.5);
        
          return User();
        }
      }
    `}
  ></AutoSnippet>

### All Ref listeners now return a way to remove the listener

It is now possible to "unsubscribe" to the various life-cycles listeners:

<AutoSnippet
  language="dart"
  codegen={`
    @riverpod
    Future<int> example(Ref ref) {
      // onDispose and other life-cycle listeners return a function
      // to remove the listener.
      final removeListener = ref.onDispose(() => print('dispose));
      // Simply call the function to remove the listener:
      removeListener();
      
      // ...
    }
  `}
  
  raw={`
    final exampleProvider = FutureProvider<int>((ref) {
      // onDispose and other life-cycle listeners return a function
      // to remove the listener.
      final removeListener = ref.onDispose(() => print('dispose));
      // Simply call the function to remove the listener:
      removeListener();
       
      // ...
    });
  `}
></AutoSnippet>

### Weak listeners - listen to a provider without preventing auto-dispose.

When using `Ref.listen`, you can optionally specify `weak: true`:

<AutoSnippet
  language="dart"
  codegen={`
    @riverpod
    Future<int> example(Ref ref) {
      ref.listen(
        anotherProvider,
        // Specify the flag
        weak: true,
        (previous, next) {},
      );
      
      // ...
    }
  `}
  
  raw={`
    final exampleProvider = FutureProvider<int>((ref) {
      ref.listen(
        anotherProvider,
        // Specify the flag
        weak: true,
        (previous, next) {},
      );
      
      // ...
    });
  `}
></AutoSnippet>

Specifying this flag will tell Riverpod that it can still dispose
the listened provider if it stops being used.

This flag is an advanced feature to help with some niche use-cases
regarding combining multiple "sources of truth" in a single provider.

[ProviderContainer]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ProviderContainer-class.html
[ProviderScope]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ProviderScope-class.html
[Mutation]: https://pub.dev/documentation/riverpod/latest/experimental_mutation/Mutation-class.html
[riverpod_lint]: https://pub.dev/packages/riverpod_lint
[Ref]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/Ref-class.html
[Ref.read]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/Ref/read.html
[tsx.get]: https://pub.dev/documentation/riverpod/latest/experimental_mutation/MutationTransaction/get.html
[WidgetRef]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/WidgetRef-class.html
[TickerMode]: https://api.flutter.dev/flutter/widgets/TickerMode-class.html
[Consumer]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/Consumer-class.html
[Notifier]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/Notifier-class.html
[AsyncValue]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/AsyncValue-class.html
[ProviderObserver]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ProviderObserver-class.html
[WidgetTester.container]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/RiverpodWidgetTesterX/container.html
[SyncProviderTransformerMixin]: https://pub.dev/documentation/riverpod/latest/misc/SyncProviderTransformerMixin-mixin.html
[ProviderListenable]: https://pub.dev/documentation/riverpod/latest/misc/ProviderListenable-class.html
[StreamProvider]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/StreamProvider-class.html
[StreamNotifier]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/StreamNotifier-class.html
[Ref.mounted]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/Ref/mounted.html